package com.xiyuan.smartutils.client.ws.core.impl.rfc6455;

import com.xiyuan.smartutils.client.ws.WebSocketHandler;
import com.xiyuan.smartutils.client.ws.WebSocketPacket;
import com.xiyuan.smartutils.client.ws.client.WebSocketClient;
import com.xiyuan.smartutils.client.ws.core.WebSocketPacketResolver;
import com.xiyuan.smartutils.client.ws.core.handshake.HttpHandshake;
import com.xiyuan.smartutils.client.ws.core.impl.WebSocketInstance;
import com.xiyuan.smartutils.client.ws.core.proxy.Proxy;
import com.xiyuan.smartutils.client.exception.WebSocketException;
import com.xiyuan.smartutils.client.ws.util.Base64;

import java.nio.ByteBuffer;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

import static com.xiyuan.smartutils.client.ws.ErrorCode.*;

/**
 * @author xiyuan-lgz
 */
@SuppressWarnings({"JavadocLinkAsPlainText", "SpellCheckingInspection"})
public class DefaultWebsocket extends WebSocketInstance {
    /**
     * The Constant VERSION.
     */
    private static final int VERSION = 13;
    
    /**
     * The extensions.
     */
    protected Set<String> extensions = new HashSet<String>();
    
    /**
     * The server extensions.
     */
    protected String[] serverExtensions;
    
    public DefaultWebsocket(String url, WebSocketHandler<WebSocketClient> handler, String[] protocols) throws WebSocketException {
        super(url, handler, protocols);
    }
    
    public DefaultWebsocket(String url, String origin, WebSocketHandler<WebSocketClient> handler, String[] protocols) throws
                                                                                                     WebSocketException {
        super(url, origin, handler, protocols);
    }
    
    public DefaultWebsocket(String url, Proxy proxy, WebSocketHandler<WebSocketClient> handler, String[] protocols) throws
                                                                                                   WebSocketException {
        super(url, proxy, handler, protocols);
    }
    
    public DefaultWebsocket(String url, String origin, Proxy proxy, WebSocketHandler<WebSocketClient> handler, String[] protocols) throws
                                                                                                                  WebSocketException {
        super(url, url, proxy, handler, protocols);
    }
    
    @Override
    protected void onClose() throws WebSocketException {
        // TODO Auto-generated method stub
        
    }
    
    @Override
    protected int getWebSocketVersion() {
        return VERSION;
    }
    
    @Override
    protected HttpHandshake newHandshakeInstance() {
        return new HttpHandshake() {
            
            private void addHeader(StringBuilder strb, String key, String value) {
                strb.append(key).append(": ").append(value).append("\r\n");
            }
            
            /**
             * Create a handshake requtest
             *
             * <pre>
             * Sample (RFC6455)
             * client => server
             *   GET /chat HTTP/1.1
             *   Host: server.example.com
             *   Upgrade: websocket
             *   Connection: Upgrade
             *   Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
             *   Origin: http://example.com
             *   Sec-WebSocket-Protocol: chat, superchat
             *   Sec-WebSocket-Version:13
             * </pre>
             *
             */
            @Override
            public ByteBuffer createHandshakeRequest() throws WebSocketException {
                StringBuilder strb = new StringBuilder().append("GET ").append(path).append(" HTTP/1.1").append("\r\n");
                addHeader(strb, "Host", endpointAddress.getHostName());
                addHeader(strb, "Upgrade", "websocket");
                addHeader(strb, "Connection", "Upgrade");
                addHeader(strb, "Sec-WebSocket-Key", generateWebSocketKey());
                if (origin != null) {addHeader(strb, "Origin", origin);}
                
                if (protocols != null && protocols.length > 0) {
                    addHeader(strb, "Sec-WebSocket-Protocol", join(",", protocols));
                }
                
                // TODO Sec-WebSocket-Extensions
                if (!extensions.isEmpty()) {addHeader(strb, "Sec-WebSocket-Extensions", join(",", extensions));}
                
                addHeader(strb, "Sec-WebSocket-Version", String.valueOf(getWebSocketVersion()));
                
                // extra headers
                if (requestHeader != null) {
                    for (String h : requestHeader.getHeaderNames()) {
                        addHeader(strb, h, requestHeader.getHeader(h));
                    }
                }
                
                strb.append("\r\n");
                return ByteBuffer.wrap(strb.toString().getBytes());
            }
            
            /**
             * check handshake response
             *
             * <pre>
             * server => client
             * HTTP/1.1 101 Switching Protocols
             * Upgrade: websocket
             * Connection: Upgrade
             * Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
             * Sec-WebSocket-Protocol: chat
             * </pre>
             *
             * @param buffer
             */
            @Override
            protected boolean parseHandshakeResponseHeader(ByteBuffer buffer) throws WebSocketException {
                if (!super.parseHandshakeResponseHeader(buffer)) {return false;}
                
                if (!"websocket".equalsIgnoreCase(this.getResponseHeader().getHeader("upgrade"))) {
                    throw new WebSocketException(E3600, responseHeader.getHeader("upgrade"));
                }
                if (!"upgrade".equalsIgnoreCase(this.getResponseHeader().getHeader("connection"))) {
                    throw new WebSocketException(E3601, responseHeader.getHeader("connection"));
                }
                if (!this.getResponseHeader().containsHeader("sec-websocket-accept")) {
                    throw new WebSocketException(E3602);
                }
                String protocolStr = this.getResponseHeader().getHeader("sec-websocket-protocol");
                if (protocolStr != null) {
                    serverProtocols = protocolStr.split(",");
                }
                String extensionsStr = this.getResponseHeader().getHeader("sec-websocket-extensions");
                if (extensionsStr != null) {
                    serverExtensions = extensionsStr.split(",");
                }
                return true;
            }
            
        };
    }
    
    @Override
    protected WebSocketPacketResolver newPacketParserInstance() {
        return WebSocketPacket.newResolver();
    }
    
    /**
     * Generate web socket key.
     *
     * @return the string
     */
    private String generateWebSocketKey() {
        // 16byte GUID
        UUID uuid = UUID.randomUUID();
        ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
        bb.putLong(uuid.getMostSignificantBits());
        bb.putLong(uuid.getLeastSignificantBits());
        return Base64.encodeToString(bb.array(), false);
    }
}
